// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <ddk/debug.h>
#include <usb/usb-request.h>
#include <string.h>

#include "xhci-transfer-common.h"

namespace usb_xhci {

void xhci_print_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
  size_t index = trb - ring->start;
  uint32_t* ptr = (uint32_t*)trb;
  auto physmap = ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(trb)));

  uint64_t paddr =
      physmap->second + (reinterpret_cast<size_t>(ring->current_trb) - physmap->first.virt_start);

  zxlogf(LSPEW, "trb[%03zu] %p: %08X %08X %08X %08X\n", index, (void*)paddr, ptr[0], ptr[1], ptr[2],
         ptr[3]);
}

void xhci_transfer_state_init(xhci_transfer_state_t* state, usb_request_t* req, uint8_t ep_type,
                              uint16_t ep_max_packet_size) {
  memset(state, 0, sizeof(*state));

  // compute number of packets needed for this transaction
  if (req->header.length > 0) {
    usb_request_phys_iter_init(&state->phys_iter, req, XHCI_MAX_DATA_BUFFER);
    zx_paddr_t dummy_paddr;
    while (usb_request_phys_iter_next(&state->phys_iter, &dummy_paddr) > 0) {
      state->packet_count++;
    }
  }

  usb_request_phys_iter_init(&state->phys_iter, req, XHCI_MAX_DATA_BUFFER);

  usb_setup_t* setup = (req->header.ep_address == 0 ? &req->setup : nullptr);
  if (setup) {
    state->direction = setup->bmRequestType & USB_ENDPOINT_DIR_MASK;
    state->needs_status = true;
  } else {
    state->direction = req->header.ep_address & USB_ENDPOINT_DIR_MASK;
  }
  state->needs_data_event = true;
  // Zero length bulk transfers are allowed. We should have at least one transfer TRB
  // to avoid consecutive event data TRBs on a transfer ring.
  // See XHCI spec, section 4.11.5.2
  state->needs_transfer_trb = ep_type == USB_ENDPOINT_BULK;

  // send zero length packet if send_zlp is set and transfer is a multiple of max packet size
  state->needs_zlp = req->header.send_zlp && (req->header.length % ep_max_packet_size) == 0;
}

zx_status_t xhci_queue_data_trbs(xhci_transfer_ring_t* ring, xhci_transfer_state_t* state,
                                 usb_request_t* req, int interrupter_target, bool isochronous) {
  usb_header_t* header = &req->header;
  uint64_t frame = header->frame;
  size_t free_trbs = xhci_transfer_ring_free_trbs(ring);

  zx_paddr_t paddr;
  size_t transfer_size = 0;
  bool first_packet = (state->phys_iter.total_iterated == 0);
  while (free_trbs > 0 &&
         (((transfer_size = usb_request_phys_iter_next(&state->phys_iter, &paddr)) > 0) ||
          state->needs_transfer_trb || state->needs_zlp)) {
    xhci_trb_t* trb = ring->current_trb;
    xhci_clear_trb(trb);
    XHCI_WRITE64(&trb->ptr, paddr);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_XFER_LENGTH_START, XFER_TRB_XFER_LENGTH_BITS,
                    static_cast<uint32_t>(transfer_size));
    // number of packets remaining after this one
    uint32_t td_size = --state->packet_count;
    if (state->needs_zlp) {
      td_size++;
    }
    XHCI_SET_BITS32(&trb->status, XFER_TRB_TD_SIZE_START, XFER_TRB_TD_SIZE_BITS, td_size);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
                    interrupter_target);
    uint32_t control_bits = TRB_CHAIN;
    if (td_size == 0) {
      control_bits |= XFER_TRB_ENT;
    }
    if (header->ep_address == 0 && first_packet) {
      // use TRB_TRANSFER_DATA for first data packet on setup requests
      control_bits |= (state->direction == USB_DIR_IN ? XFER_TRB_DIR_IN : XFER_TRB_DIR_OUT);
      trb_set_control(trb, TRB_TRANSFER_DATA, control_bits);
    } else if (isochronous && first_packet) {
      // use TRB_TRANSFER_ISOCH for first data packet on isochronous endpoints
      if (frame == 0) {
        // set SIA bit to schedule packet ASAP
        control_bits |= XFER_TRB_SIA;
      } else {
        // schedule packet for specified frame
        control_bits |= ((static_cast<uint32_t>(frame % 2048) << XFER_TRB_FRAME_ID_START) &
                         XHCI_MASK(XFER_TRB_FRAME_ID_START, XFER_TRB_FRAME_ID_BITS));
      }
      trb_set_control(trb, TRB_TRANSFER_ISOCH, control_bits);
    } else {
      trb_set_control(trb, TRB_TRANSFER_NORMAL, control_bits);
    }
    if (driver_get_log_flags() & DDK_LOG_SPEW)
      xhci_print_trb(ring, trb);
    xhci_increment_ring(ring);
    free_trbs--;

    first_packet = false;
    state->needs_transfer_trb = false;
    if (transfer_size == 0) {
      // ZLP (if there was one) has been sent
      state->needs_zlp = false;
    }
  }

  if (state->phys_iter.total_iterated < header->length) {
    // still more data to queue, but we are out of TRBs.
    // come back and finish later.
    return ZX_ERR_SHOULD_WAIT;
  }

  if (state->needs_data_event) {
    if (free_trbs == 0) {
      // will need to do this later
      return ZX_ERR_SHOULD_WAIT;
    }

    // Queue event data TRB
    xhci_trb_t* trb = ring->current_trb;
    xhci_clear_trb(trb);
    trb->ptr = reinterpret_cast<uint64_t>(req);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
                    interrupter_target);
    trb_set_control(trb, TRB_TRANSFER_EVENT_DATA, XFER_TRB_IOC);
    if (driver_get_log_flags() & DDK_LOG_SPEW)
      xhci_print_trb(ring, trb);
    xhci_increment_ring(ring);
    free_trbs--;
    state->needs_data_event = false;
  }
  return ZX_OK;
}

}  // namespace usb_xhci
